package com.fengxu.gamescript.jsengine.core;

import android.content.Context;
import android.content.Intent;
import android.net.Uri;
import android.os.Handler;
import android.provider.Settings;
import android.util.Base64;
import android.util.Log;

import com.fengxu.gamescript.data.GlobalData;
import com.fengxu.gamescript.jsengine.annotation.JsCallJava;
import com.fengxu.gamescript.jsengine.annotation.JsCodeTip;
import com.fengxu.gamescript.jsengine.annotation.JsObject;
import com.fengxu.gamescript.jsengine.api.AbstractJsObject;
import com.fengxu.gamescript.jsengine.api.Console;
import com.fengxu.gamescript.jsengine.api.music.GenshinAutoClick;
import com.fengxu.gamescript.jsengine.api.Global;
import com.fengxu.gamescript.jsengine.api.IJsObject;
import com.fengxu.gamescript.jsengine.exception.ScriptInterruptException;
import com.fengxu.gamescript.jsengine.exception.ScriptRunningException;
import com.fengxu.gamescript.service.AutoAccessibilityService;
import com.fengxu.gamescript.ui.base.BaseActivity;
import com.fengxu.gamescript.util.CommonUtils;
import com.fengxu.gamescript.util.PermissionUtils;
import com.fengxu.gamescript.util.ReflectUtils;
import com.fengxu.script.JsEngine;
import com.xuexiang.xui.widget.toast.XToast;

import org.mozilla.javascript.WrappedException;

import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.TreeMap;
import java.util.Vector;

/**
 * 每运行一个，创建一个该环境
 */
public class JsRuntime{

    // 上下文对象
    private Context mContext;

    // 标识
    private String name;

    // Js引擎
    private JsEngine jsEngine;

    // js代码
    private String code;

    // 脚本开始运行的时刻
    private long startTime = 0;

    // 运行脚本的当前线程
    private Thread mThread;

    /**
     * 存放待加载的apiCLass
     */
    private static List<Class> willLoadApiLIst = new Vector();

    /**
     * 注入脚本中的所有方法/字段的提示 press,ys.press
     * 方法/属性 -> 作用
     */
    public static Map<String,String> codeTipMap = new TreeMap();

    static {
        synchronized (JsRuntime.class){
            // 存放待加载的api类,global手动加载
            willLoadApiLIst.add(Console.class);
            willLoadApiLIst.add(GenshinAutoClick.class);
            // 设置列表为不可变
            willLoadApiLIst = Collections.unmodifiableList(willLoadApiLIst);
            // 扫描代码提示信息
            scanCodeTipWithJsObject(Global.class);
            for (Class aClass : willLoadApiLIst) {
                scanCodeTipWithJsObject(aClass);
            }
            // 设置集合为不可变
            codeTipMap = Collections.unmodifiableMap(codeTipMap);
        }
    }

    /**
     * 初始化js运行环境
     * @param context   界面上下文对象
     * @param name  该runner的标识，一般可设置为文件名或路径
     * @param code  将要执行的js代码
     */
    public JsRuntime(String name, Context context,  String code) {
        this.mContext = context;
        this.code = code;
        this.name = name;
        jsEngine = new JsEngine();
        getUiHandler();
    }

    /**
     * 获取运行环境的标识
     * @return
     */
    public String getName() {
        return name;
    }

    /**
     * 获取uiHandler
     */
    public synchronized Handler getUiHandler(){
        return BaseActivity.Companion.getUiHandler();
    }

    /**
     * 检测运行环境
     * @return 是否具备运行环境
     */
    private boolean detectEnv(){
        PermissionUtils permissionUtils = PermissionUtils.INSTANCE;
        // 检测悬浮窗权限
        if(!permissionUtils.checkFloatPermission(mContext)){
            XToast.warning(mContext,"请先赋予悬浮窗选项后再尝试运行!").show();
            Intent intent = new Intent(Settings.ACTION_MANAGE_OVERLAY_PERMISSION);
            intent.setData(Uri.parse("package:" + mContext.getPackageName()));
            mContext.startActivity(intent);
            return false;
        }
        // 检测无障碍服务
        if(!permissionUtils.accessibilityIsEnabled()){
            XToast.warning(mContext,"请先打开无障碍服务后再尝试运行!").show();
            Intent intent = new Intent(Settings.ACTION_ACCESSIBILITY_SETTINGS);
            intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
            mContext.startActivity(intent);
            return false;
        }
        return true;
    }

    /**
     * 开始运行脚本
     */
    public void start() {
        if(!detectEnv()) return;
        Log.i("脚本消息", String.format("[%s]开始运行!", name));
        startTime = System.currentTimeMillis();
        GlobalData.INSTANCE.addLog(new JsRunnerLog(name,JsRunnerLog.LogLevel.System, "开始运行!"));
        GlobalData.INSTANCE.addRunningScript(this);
        mThread = new Thread(()->{
            jsEngine.reset();
            loadModules();
            try {
                jsEngine.exec(tryDecryptScriptCode(code));
            }catch (IllegalStateException e){
                // 这里是手动调用脚本强制退出触发的异常，不用管
                Log.i("脚本消息", "用户强制退出");
                GlobalData.INSTANCE.addLog(new JsRunnerLog(name,JsRunnerLog.LogLevel.System,"用户强制退出"));
                jsEngine.dispatchError(e);
            }
            catch (ScriptInterruptException e){
                // 用户强制退出异常
                Log.i("脚本消息", e.getMessage());
                GlobalData.INSTANCE.addLog(new JsRunnerLog(name,JsRunnerLog.LogLevel.System, e.getMessage()));
                jsEngine.dispatchError(e);
            }
            catch (WrappedException e){
                Log.i("脚本消息", e.getMessage());
                jsEngine.dispatchError(e.getWrappedException());
                if(e.getWrappedException() instanceof ScriptInterruptException){
                    // 用户强制退出
                    GlobalData.INSTANCE.addLog(new JsRunnerLog(name,JsRunnerLog.LogLevel.System, "用户强制退出"));
                }else{
                    String errorMsg = e.getWrappedException().getMessage() == null ? "": e.getWrappedException().getMessage();
                    GlobalData.INSTANCE.addLog(new JsRunnerLog(name,JsRunnerLog.LogLevel.Error, errorMsg));
                }
            }catch (Exception e){
                Log.i("脚本消息", e.getMessage());
                jsEngine.dispatchError(e);
                GlobalData.INSTANCE.addLog(new JsRunnerLog(name,JsRunnerLog.LogLevel.Error, "脚本执行发生错误:" + e.getMessage()));
            }finally {
                onExit();
            }
        },name);
        mThread.start();
    }


    /**
     * 扫描从可注入到js环境的对象上获取到所有的函数/字段提示
     * @param tClass 目标类型
     */
    private static void scanCodeTipWithJsObject(Class<? extends IJsObject> tClass){
        JsObject jsObject = tClass.getAnnotation(JsObject.class);
        if(jsObject == null) throw new NullPointerException("该类型必须标注了@JsObject注解在可以智能提示!");
        String nameInJs = jsObject.name();
        // 名称以下划线开始的认定为私有对象只扫描其带有@JsCallJava的公开属性/方法
        boolean isPublic = !nameInJs.startsWith("_");
        // 扫描字段
        for (Field field : tClass.getFields()) {
            if(!ReflectUtils.isAnnotationPresent(field,JsCodeTip.class)) continue;
            String fieldName;
            String tip = "";
            if(field.isAnnotationPresent(JsCallJava.class)){
                JsCallJava annotation = field.getAnnotation(JsCallJava.class);
                fieldName = annotation.value().isEmpty() ? field.getName() : annotation.value();
                tip = annotation.statement();
                codeTipMap.put(fieldName,tip);
            }else{
                fieldName = nameInJs + "." + field.getName();
                // 未有@JsCallJava的方法必须是对象在js环境下是公开的才可以访问
                if(isPublic){
                    codeTipMap.put(fieldName,tip);
                }
            }
        }
        // 扫描方法
        for (Method method : tClass.getMethods()) {
            if(!ReflectUtils.isAnnotationPresent(method,JsCodeTip.class)) continue;
            String methodName;
            String tip = "";
            if(method.isAnnotationPresent(JsCallJava.class)){
                JsCallJava annotation = method.getAnnotation(JsCallJava.class);
                methodName = annotation.value().isEmpty() ? method.getName() : annotation.value();
                tip = annotation.statement();
                codeTipMap.put(methodName,tip);
            }else{
                methodName = nameInJs + "." + method.getName();
                if(isPublic){
                    codeTipMap.put(methodName, tip);
                }
            }
        }
    }

    /**
     * 加载脚本所需所有模块
     */
    private void loadModules(){
        // 初始化全局对象
        Global global = new Global(jsEngine, mContext, BaseActivity.Companion.getUiHandler());
        loadModuleImpl(global);
        // 从待加载class列表中加载所有脚本依赖模块
        for (Class apiClass : willLoadApiLIst) {
            if(AbstractJsObject.class.isAssignableFrom(apiClass)){
                try {
                    Constructor constructor = apiClass.getConstructor(Global.class);
                    Object instance = constructor.newInstance(global);
                    loadModuleImpl((AbstractJsObject) instance);
                }catch (Exception e){
                    e.printStackTrace();
                }
            }
        }
    }

    /**
     * 加载单个脚本所需模块实现
     */
    private void loadModuleImpl(IJsObject jsObject){
        jsEngine.injectProp(jsObject,true);
    }

    /**
     * 尝试使用内部工具解密脚本代码,失败则以源代码运行
     * @param code
     * @return
     */
    private String tryDecryptScriptCode(String code){
        if(CommonUtils.INSTANCE.isBase64(code)){
            byte[] bytes = Base64.decode(code, Base64.DEFAULT);
            bytes = CommonUtils.INSTANCE.k(bytes);
            return new String(bytes);
        }
        return code;
    }

    /**
     * 退出脚本时调用的方法
     */
    private void onExit(){
        jsEngine.exit();
        long useTime = System.currentTimeMillis() - startTime;
        GlobalData.INSTANCE.addLog(new JsRunnerLog(name,JsRunnerLog.LogLevel.System, "运行结束,用时：" + String.format("%.2f秒!",useTime/1000.0)));
        Log.i("脚本消息", String.format("[%s]脚本退出,用时: %.2f秒!",name,useTime/1000.0));
        BaseActivity.Companion.getUiHandler().post(()->{ GlobalData.INSTANCE.removeStopedScript(this); });
    }

    /**
     * 强制退出脚本环境
     */
    public void forceExit(){
        if(mThread == null) throw new ScriptRunningException("脚本未开始执行!");
        mThread.interrupt();
    }


}
